package com.clp.protocol.iec104.client.pipeline.handler;

/**
 * 每个新创建的连接都会被分配到这个Handler，该handler会绑定通道的相关信息alive104Channel
 * ChannelHandler被包装在ChannelHandlerContext实例中。而ChannelHandlerContext就是ChannelPipeline链表上的节点类型。
 * <p>
 * 生命周期：
 * 1、先判断这个Handler是否可以共享，即 isSharable();
 * 2、添加Handler，处理添加事件为 handlerAdded();
 * 3、对channel进行register，处理注册事件为 channelRegistered();
 * 4、register成功后，channel处于active状态，处理active事件为 channelActive();
 * 5、接下来可以对channel中的数据进行read，处理read事件为 channelRead();
 * 6、读完后，进入readComplete状态，处理该事件为 channelReadComplete();
 * 7、读完之后进入inActive状态，处理该事件为 channelInActive();
 * 8、取消注册，即channelUnRegistered，处理该事件为 channelUnregistered();
 * 9、移除handler，即handlerRemoved，处理该事件为 handlerRemoved();
 */

import com.clp.protocol.iec104.apdu.Apdu;
import com.clp.protocol.iec104.client.CouldNotAutoReconnectException;
import com.clp.protocol.iec104.client.Iec104Client;
import com.clp.protocol.iec104.client.Master;
import com.clp.protocol.iec104.client.MasterImpl;
import com.clp.protocol.iec104.client.async.MasterFuture;
import com.clp.protocol.iec104.client.async.MasterFutureListener;
import com.clp.protocol.iec104.client.pipeline.DuplexMasterHandler;
import com.clp.protocol.iec104.exception.IncorrectRtuAddrException;
import com.clp.protocol.iec104.exception.IncorrectSeqException;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledFuture;

/**
 * 用于控制收发过程
 */
@Slf4j
public class ControlMasterHandler extends DuplexMasterHandler {
    // 需要周期性执行的任务列表，让该eventLoop的线程执行
    private final List<ScheduledFuture<?>> scheduledFutures = new ArrayList<>();

    public ControlMasterHandler(Master master) {
        super(master);
    }

    @Override
    public void whenActive(ChannelHandlerContext ctx) throws Exception {
        passActive(ctx);
    }

    /**
     * 接收到 {@link Apdu} 时要保证后续更新状态时2个通道仅有一个放行apdu
     * @param ctx
     * @param apdu
     */
    @Override
    public void whenRecvingApdu(ChannelHandlerContext ctx, Apdu apdu) {
        if (apdu == null) return;

        MasterImpl masterImpl = (MasterImpl) master;

        // 检查和更新所有状态
        try {
            log.info("[Control] 接收Apdu：{}", apdu);
            apdu = masterImpl.handleRecving(apdu);
        } catch (IncorrectRtuAddrException e) {
            e.printStackTrace();
            log.warn("[Control] 接收Apdu的Rtu地址不匹配，已丢弃！");
            apdu = null;
        } catch (IncorrectSeqException e) {
            e.printStackTrace();
            log.warn("[Control] I帧失序，即将重连...");
            Iec104Client.get().reconnect(masterImpl);
        }
        if (apdu == null) return;

        passRecvingApdu(ctx, apdu);
    }

    @Override
    public void whenSendingApdu(ChannelHandlerContext ctx, Apdu apdu, ChannelPromise promise) {
        if (apdu == null) return;

        MasterImpl masterImpl = (MasterImpl) this.master;

        // 检查和更新所有状态
        apdu = masterImpl.handleSending(apdu);
        if (apdu == null) return;
        log.info("[Control] 发送 Apdu{}", apdu);

        passSendingApdu(ctx, apdu, promise);
    }

    @Override
    public void whenInActive(ChannelHandlerContext ctx) throws Exception {
        MasterImpl masterImpl = (MasterImpl) this.master;

        // 取消所有循环任务
        for (ScheduledFuture<?> scheduledFuture : scheduledFutures) {
            scheduledFuture.cancel(true);
        }

        // 尝试重连
        try {
            masterImpl.autoReconnect().addListener(new MasterFutureListener<Void>() {
                @Override
                public void operationComplete(MasterFuture<Void> future) {
                    if (!future.isSuccess()) {
                        log.info("自动重连失败，原因：{}，即将断开这个连接", future.cause().getMessage());
                        Iec104Client.get().disconnect(masterImpl);
                    }
                }
            });
        } catch (CouldNotAutoReconnectException ex) {
            log.info("取消自动重连，原因：" + ex.getMessage());
        }

        passInActive(ctx);
    }

    @Override
    public void whenExceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        passExceptionCaught(ctx, cause);
    }
}
